﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data.Common;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data;

namespace BOHVote.Core
{
    public sealed class TransactionManager : ITransactionManager
    {
        #region <<  Fields  >>
        private DbConnection connection;
        private bool disposed;
        private static object syncRoot = new object();
        #endregion

        #region <<  Constructor  >>

        /// <summary>
        ///	Initializes a new instance of the <see cref="TransactionManager"/> class.
        /// </summary>
        public TransactionManager()
        {
            this.database = DatabaseFactory.CreateDatabase();
            this.connection = this.database.CreateConnection();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TransactionManager"/> class.
        /// </summary>
        /// <param name="connectionString">The instance name of the database.</param>
        public TransactionManager(string instanceName)
        {
            if (string.IsNullOrEmpty(instanceName))
            {
                throw new ArgumentNullException(Constant.DATABASE_NULL_EXCEPTION_MESSAGE);
            }
            else
            {
                this.database = DatabaseFactory.CreateDatabase(instanceName);
                this.connection = this.database.CreateConnection();
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TransactionManager"/> class.
        /// </summary>
        /// <param name="database">The instance of the database.</param>
        public TransactionManager(Database database)
        {
            this.database = database;
            this.connection = this.database.CreateConnection();
        }

        #endregion

        #region <<  Parameters  >>

        /// <summary>
        /// Gets or sets the configuration key for database service.
        /// </summary>
        /// <remark>Do not change during a transaction.</remark>
        /// <exception cref="InvalidOperationException">
        /// If an attempt to set when the connection is currently open.
        /// </exception>
        public string ConnectionString
        {
            get
            {
                return this.connection.ConnectionString;
            }
        }

        private Database database;
        /// <summary>
        /// Gets the <see cref="Database"/> instance.
        /// </summary>
        /// <value></value>
        public Database Database
        {
            get
            {
                return this.database;
            }
        }

        private DbTransaction transaction;
        /// <summary>
        ///	Gets the underlying <see cref="DbTransaction"/> object.
        /// </summary>
        public DbTransaction TransactionObject
        {
            get
            {
                return this.transaction;
            }
        }

        private bool transactionOpen;
        /// <summary>
        ///	Gets a value that indicates if a transaction is currently open and operating. 
        /// </summary>
        /// <value>Return true if a transaction session is currently open and operating; otherwise false.</value>
        public bool IsOpen
        {
            get
            {
                return transactionOpen;
            }
        }

        #endregion

        #region <<  Public Method  >>

        /// <summary>
        /// Begins a transaction.
        /// </summary>
        /// <remarks>The default <see cref="IsolationLevel"/> mode is ReadCommitted</remarks>
        /// <exception cref="InvalidOperationException">If a transaction is already open.</exception>
        public void BeginTransaction()
        {
            BeginTransaction(IsolationLevel.ReadCommitted);
        }

        /// <summary>
        /// Begins a transaction.
        /// </summary>
        /// <param name="isolationLevel">The <see cref="IsolationLevel"/> level of the transaction</param>
        /// <exception cref="InvalidOperationException">If a transaction is already open.</exception>
        /// <exception cref="DataException"></exception>
        /// <exception cref="DbException"></exception>
        public void BeginTransaction(IsolationLevel isolationLevel)
        {
            if (IsOpen)
            {
                throw new InvalidOperationException(Constant.TRANSACTION_ALREADY_OPEN_EXCEPTION_MESSAGE);
            }
            try//Open connection
            {
                if (!this.IsOpen)
                {
                    this.connection.Open();
                    this.transaction = this.connection.BeginTransaction(isolationLevel);
                    this.transactionOpen = true;
                }
            }
            catch (Exception)
            {
                // in the event of an error, close the connection and destroy the transobject.
                if (this.connection != null)
                    this.connection.Close();
                if (this.transaction != null)
                    this.transaction.Dispose();
                this.transactionOpen = false;
                throw;
            }
        }

        /// <summary>
        /// Commit the transaction to the datasource.
        /// </summary>
        /// <exception cref="InvalidOperationException">If a transaction is not open.</exception>
        public void Commit()
        {
            if (!this.IsOpen)
            {
                throw new InvalidOperationException(Constant.TRANSACTION_NEED_BEGIN_FIRST_EXCEPTION_MESSAGE);
            }
            try
            {
                if (this.IsOpen)
                {
                    this.transaction.Commit(); // SqlClient could throw Exception or InvalidOperationException
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                //assuming the commit was sucessful.
                this.connection.Close();
                this.transaction.Dispose();
                this.transactionOpen = false;
            }
        }

        /// <summary>
        ///	Rollback the transaction.
        /// </summary>
        /// <exception cref="InvalidOperationException">If a transaction is not open.</exception>
        public void Rollback()
        {
            if (!this.IsOpen)
            {
                throw new InvalidOperationException(Constant.TRANSACTION_NEED_BEGIN_FIRST_EXCEPTION_MESSAGE);
            }
            try
            {
                if (this.IsOpen)
                {
                    this.transaction.Rollback(); // Database Client could throw Exception or InvalidOperationException
                }
            }
            catch (Exception)
            {
                throw;
            }
            finally
            {
                this.connection.Close();
                if (this.transaction != null)
                {
                    this.transaction.Dispose();
                }
                this.transactionOpen = false;
            }
        }

        #endregion

        #region <<  Inplement IDispose Interface  >>

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                lock (syncRoot)
                {
                    disposed = true;

                    if (this.IsOpen)
                    {
                        this.Rollback();
                    }
                }
            }
        }

        #endregion
    }
}
